/**
 * Copyright 2010 Victor Egea Hernando
 *
 * RockScript is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * RockScript is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with RockScript.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "pjit_asm_x86.h"

#include "../../pjit_block.h"
#include "../../pjit_buffer.h"

#include <iostream>
#include <sstream>

#if PICOJIT_TARGET == 1 || PICOJIT_TARGET == 2

// Increase block size if necessary
#define RESERVE_BLOCK( NBYTES ) \
	if( _block->gap() < NBYTES ) _block->reserve( _block->size() + 1024 );

// Register tables
char *name_08breg[] = { "%al",   "%cl",   "%dl",   "%bl",   "%ah",   "%ch",   "%dh",   "%bh"   };
char *name_32breg[] = { "%eax",  "%ecx",  "%edx",  "%ebx",  "%esp",  "%ebp",  "%esi",  "%ebi"  };
char *name_16breg[] = { "%ax" ,  "%cx",   "%dx",   "%bx",   "%sp",   "%bp1",  "%si",   "%di"   };
char *name_mmreg[]  = { "%mm0",  "%mm1",  "%mm2",  "%mm3",  "%mm4",  "%mm5",  "%mm6",  "%mm7"  };
char *name_xmmreg[] = { "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7" };

namespace pjit{

pjit_asm_x86::pjit_asm_x86( pjit_block &block, uint32 bytes ){
	_block = &block;
	// assign memory to block
	if( bytes ) _block->reserve( bytes );
}

pjit_asm_x86 &pjit_asm_x86::ret( void ){
	// print assembler
	if( _block->back_asm() ){
		help_print_format() << "      ret"; help_print_offset();
	}
	// generate binary opcode
	RESERVE_BLOCK( 1 );
	_block->push_1byte( OP_RET_C3 ); // C3
	// return reference
	return( *this );
}

pjit_asm_x86 &pjit_asm_x86::leave( void ){
	// print assembler
	if( _block->back_asm() ){
		help_print_format() << "      leave"; help_print_offset();
	}
	// generate binary opcode
	RESERVE_BLOCK( 1 );
	_block->push_1byte( OP_LEAVE_C9 ); // C9
	// return reference
	return( *this );
}

pjit_asm_x86 &pjit_asm_x86::mov_imm32_to_reg32( uint32 reg32, uint32 imm32, uint32 pmode ){
	// print assembler
	if( _block->back_asm() ){
		help_print_format() << "      mov " + help_print_imm32( imm32, pmode )
		   + "," + name_32breg[ reg32 ]; help_print_offset();
	}
	// generate binary opcode
	RESERVE_BLOCK( 5 );
	_block->push_1byte( OP_MOV_B8 | reg32 ); // B8 + rw
	_block->push_4bytes( imm32 );
	// return reference
	return( *this );
}

pjit_asm_x86 &pjit_asm_x86::add_imm32_to_reg32( uint32 reg32, uint32 imm32, uint32 pmode ){
	// print assembler
	if( _block->back_asm() ){
		help_print_format() << "      add " + help_print_imm32( imm32, pmode )
		    + "," + name_32breg[ reg32 ]; help_print_offset();
	}
	// generate binary opcode
	RESERVE_BLOCK( 6 );
	_block->push_1byte( OP_SPECIAL_81 ); // 81 /0
	_block->push_1byte( help_modrm( MODRM_REG32, 0, reg32 ) );
	_block->push_4bytes( imm32 );
	// return reference
	return( *this );
}

pjit_asm_x86 &pjit_asm_x86::sub_imm32_to_reg32( uint32 reg32, uint32 imm32, uint32 pmode ){
	// print assembler
	if( _block->back_asm() ){
		help_print_format() << "      sub " + help_print_imm32( imm32, pmode )
		   + "," + name_32breg[ reg32 ]; help_print_offset();
	}
	// generate binary opcode
	RESERVE_BLOCK( 6 );
	_block->push_1byte( OP_SPECIAL_81 ); // 81 /5
	_block->push_1byte( help_modrm( MODRM_REG32, 5, reg32 ) );
	_block->push_4bytes( imm32 );
	// return reference
	return( *this );
}

pjit_asm_x86 &pjit_asm_x86::mov_reg32_to_reg32( uint32 reg32a, uint32 reg32b ){
	// print assembler
	if( _block->back_asm() ){
		help_print_format() << std::string("      mov ") + name_32breg[ reg32a ]
		   + "," + name_32breg[ reg32b ]; help_print_offset();
	}
	// generate binary opcode
	RESERVE_BLOCK( 6 );
	_block->push_1byte( OP_MOV_8B ); // 8B
	_block->push_1byte( help_modrm( MODRM_REG32, reg32b, reg32a ) );
	// return reference
	return( *this );
}

pjit_asm_x86 &pjit_asm_x86::push_imm32( uint32 imm32, uint32 pmode ){
	// print assembler
	if( _block->back_asm() ){
		help_print_format() << std::string("      push ")
			+ help_print_imm32( imm32, pmode );
		help_print_offset();
	}
	// generate binary opcode
	RESERVE_BLOCK( 5 );
	_block->push_1byte( OP_PUSH_68 ); // 68
	_block->push_4bytes( imm32 );
	// return reference
	return( *this );
}

pjit_asm_x86 &pjit_asm_x86::push_reg32( uint32 reg32 ){
	// print assembler
	if( _block->back_asm() ){
		help_print_format() << std::string("      push ") + name_32breg[reg32];
		help_print_offset();
	}
	// generate binary opcode
	RESERVE_BLOCK( 2 );
	_block->push_1byte( OP_SPECIAL_FF ); // FF /6
	_block->push_1byte( help_modrm( MODRM_REG32, 6, reg32 ) );
	// return reference
	return( *this );
}

pjit_asm_x86 &pjit_asm_x86::far_call_reg32( uint32 reg32 ){
	// print assembler
	if( _block->back_asm() ){
		help_print_format() << std::string("      call *") + name_32breg[reg32];
		help_print_offset();
	}
	// generate binary opcode
	RESERVE_BLOCK( 2 );
	_block->push_1byte( OP_SPECIAL_FF ); // FF /2
	_block->push_1byte( help_modrm( MODRM_REG32, 2, reg32 ) );
	// return reference
	return( *this );
}

std::string pjit_asm_x86::help_print_imm32( uint32 imm32, uint32 pmode ){
	std::stringstream sstream;
	// print mode
	if( pmode & PRINT_HEX ){
		sstream << "$0x" << std::hex << imm32;
	}else if( pmode & PRINT_REAL ){
		sstream << "$" << static_cast<real32>( imm32 );
	}else if( pmode & PRINT_INT ){
		sstream << "$" << static_cast<int32>( imm32 );
	}else if( pmode & PRINT_UINT ){
		sstream << "$" << imm32;
	}
	// return reference
	return( sstream.str() );
}

std::ostream &pjit_asm_x86::help_print_format( void ){
	_block->asm_os().width( 40 ); _block->asm_os().fill(' ');
	return( _block->asm_os() << std::left );
}

pjit_block &pjit_asm_x86::block( void ){
	return( *_block );
}

std::ostream &pjit_asm_x86::help_print_offset( void ){
	_block->asm_os() << " ;offset: 0x";
	_block->asm_os().fill('0'); _block->asm_os().width( 6 );
	_block->asm_os() << std::right << std::hex << _block->offset() << std::endl;
	// return reference
	return( _block->asm_os() );
}

uint8 pjit_asm_x86::help_modrm( uint32 mod, uint32 reg_op, uint32 rm ){
	return( ( mod << 6 ) | ( reg_op << 3 ) | rm );
}

uint8 pjit_asm_x86::help_sib( uint32 ss, uint32 reg_op, uint32 index ){
	return( ( ss << 6 ) | ( reg_op << 3 ) | index );
}

};

#endif // PICOJIT_TARGET == 1 || PICOJIT_TARGET == 2
